package com.fanwj.citrus.aidl.proxy;

import android.os.BadParcelableException;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.reflect.Array;

public class ParcelProxy {

    protected final Parcel impl;

    protected <T>ParcelProxy(final T impl) {
        this.impl = impl instanceof Parcel ? (Parcel)impl : Parcel.obtain();
    }

    public Parcel get() {
        return impl;
    }

    public void recycle() {
        impl.recycle();
    }

    public int dataSize() {
        return impl.dataSize();
    }

    public int dataPosition() {
        return impl.dataPosition();
    }

    public void setDataSize(int size) {
        impl.setDataSize(size);
    }

    public void setDataPosition(int pos) {
        impl.setDataPosition(pos);
    }

    public void writeInterfaceToken(@NonNull String interfaceName) {
    }

    public void enforceInterface(@NonNull String interfaceName) {
    }

    public void writeByteArray(@Nullable byte[] b) {
        impl.writeByteArray(b);
    }

    public void writeInt(int val) {
        impl.writeInt(val);
    }

    public void writeLong(long val) {
        impl.writeLong(val);
    }

    public void writeFloat(float val) {
        impl.writeFloat(val);
    }

    public void writeDouble(double val) {
        impl.writeDouble(val);
    }

    public void writeString(@Nullable String val) {
        impl.writeString(val);
    }

    public void writeByte(byte val) {
        impl.writeByte(val);
    }

    public void writeBooleanArray(@Nullable boolean[] val) {
        impl.writeBooleanArray(val);
    }

    public boolean[] createBooleanArray() {
        return impl.createBooleanArray();
    }

    public void readBooleanArray(@NonNull boolean[] val) {
        impl.readBooleanArray(val);
    }

    public void writeCharArray(@Nullable char[] val) {
        impl.writeCharArray(val);
    }

    public char[] createCharArray() {
        return impl.createCharArray();
    }

    public void readCharArray(@NonNull char[] val) {
        impl.readCharArray(val);
    }

    public void writeIntArray(@Nullable int[] val) {
        impl.writeIntArray(val);
    }

    public int[] createIntArray() {
        return impl.createIntArray();
    }

    public void readIntArray(@NonNull int[] val) {
        impl.readIntArray(val);
    }

    public void writeLongArray(@Nullable long[] val) {
        impl.writeLongArray(val);
    }

    public long[] createLongArray() {
        return impl.createLongArray();
    }

    public void readLongArray(@NonNull long[] val) {
        impl.readLongArray(val);
    }

    public void writeFloatArray(@Nullable float[] val) {
        impl.writeFloatArray(val);
    }

    public float[] createFloatArray() {
        return impl.createFloatArray();
    }

    public void readFloatArray(@NonNull float[] val) {
        impl.readFloatArray(val);
    }

    public void writeDoubleArray(@Nullable double[] val) {
        impl.writeDoubleArray(val);
    }

    public double[] createDoubleArray() {
        return impl.createDoubleArray();
    }

    public void readDoubleArray(@NonNull double[] val) {
        impl.readDoubleArray(val);
    }

    public void writeStringArray(@Nullable String[] val) {
        impl.writeStringArray(val);
    }

    public String[] createStringArray() {
        return impl.createStringArray();
    }

    public void readStringArray(@NonNull String[] val) {
        impl.readStringArray(val);
    }

    public <T extends Parcelable> void writeTypedArray(@Nullable T[] val, int parcelableFlags) {
        impl.writeTypedArray(val, parcelableFlags);
    }

    private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index,
                                             int[] dimensions) {
        if (index >= dimensions.length) {
            throw new BadParcelableException("Array has more dimensions than expected: "
                    + dimensions.length);
        }

        int length = dimensions[index];

        // val should be an array of length N
        if (val == null) {
            throw new BadParcelableException("Non-null array shouldn't have a null array.");
        }
        if (!val.getClass().isArray()) {
            throw new BadParcelableException("Not an array: " + val);
        }
        if (Array.getLength(val) != length) {
            throw new BadParcelableException("bad length: expected " + length + ", but got "
                    + Array.getLength(val));
        }

        // Delegates to other writers if this is a one-dimensional array.
        // Otherwise, write component arrays with recursive calls.

        final Class<?> componentType = val.getClass().getComponentType();
        if (!componentType.isArray() && index + 1 != dimensions.length) {
            throw new BadParcelableException("Array has fewer dimensions than expected: "
                    + dimensions.length);
        }
        if (componentType == boolean.class) {
            writeBooleanArray((boolean[]) val);
        } else if (componentType == byte.class) {
            writeByteArray((byte[]) val);
        } else if (componentType == char.class) {
            writeCharArray((char[]) val);
        } else if (componentType == int.class) {
            writeIntArray((int[]) val);
        } else if (componentType == long.class) {
            writeLongArray((long[]) val);
        } else if (componentType == float.class) {
            writeFloatArray((float[]) val);
        } else if (componentType == double.class) {
            writeDoubleArray((double[]) val);
        } else if (val instanceof String[]) {
            writeStringArray((String[]) val);
        } else if (Parcelable.class.isAssignableFrom(componentType)) {
            writeTypedArray((Parcelable[]) val, parcelableFlags);
        } else if (componentType.isArray()) {
            writeInt(length);
            for (int i = 0; i < length; i++) {
                writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1,
                        dimensions);
            }
        } else {
            throw new BadParcelableException("unknown type for fixed-size array: " + componentType);
        }
    }

    public <T> void writeFixedArray(@Nullable T val, int parcelableFlags, @NonNull int... dimensions) {
        if (val == null) {
            writeInt(-1);
            return;
        }
        writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions);
    }

    public void writeNoException() {
        impl.writeNoException();
    }

    public void readException() {
        impl.readException();
    }

    public int readInt() {
        return impl.readInt();
    }

    public long readLong() {
        return impl.readLong();
    }

    public float readFloat() {
        return impl.readFloat();
    }

    public double readDouble() {
        return impl.readDouble();
    }

    public String readString() {
        return impl.readString();
    }

    public byte readByte() {
        return impl.readByte();
    }

    public Size readSize() {
        return impl.readSize();
    }

    public byte[] createByteArray() {
        return impl.createByteArray();
    }

    public void readByteArray(@NonNull byte[] val) {
        impl.readByteArray(val);
    }

    public <T> T[] createTypedArray(@NonNull Parcelable.Creator<T> c) {
        return impl.createTypedArray(c);
    }

    public <T> void readTypedArray(@NonNull T[] val, @NonNull Parcelable.Creator<T> c) {
        impl.readTypedArray(val, c);
    }

    public <T> void readFixedArray(@NonNull T val) {
        Class<?> componentType = val.getClass().getComponentType();
        if (componentType == boolean.class) {
            readBooleanArray((boolean[]) val);
        } else if (componentType == byte.class) {
            readByteArray((byte[]) val);
        } else if (componentType == char.class) {
            readCharArray((char[]) val);
        } else if (componentType == int.class) {
            readIntArray((int[]) val);
        } else if (componentType == long.class) {
            readLongArray((long[]) val);
        } else if (componentType == float.class) {
            readFloatArray((float[]) val);
        } else if (componentType == double.class) {
            readDoubleArray((double[]) val);
        } else if (val instanceof String[]) {
            readStringArray((String[])val);
        } else if (componentType.isArray()) {
            int length = readInt();
            if (length != Array.getLength(val)) {
                throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
                        + ", but got " + length);
            }
            for (int i = 0; i < length; i++) {
                readFixedArray(Array.get(val, i));
            }
        } else {
            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
        }
    }

    public <T, S extends Parcelable> void readFixedArray(@NonNull T val, @NonNull Parcelable.Creator<S> c) {
        Class<?> componentType = val.getClass().getComponentType();
        if (Parcelable.class.isAssignableFrom(componentType)) {
            readTypedArray((S[]) val, c);
        } else if (componentType.isArray()) {
            int length = readInt();
            if (length != Array.getLength(val)) {
                throw new BadParcelableException("Bad length: expected " + Array.getLength(val)
                        + ", but got " + length);
            }
            for (int i = 0; i < length; i++) {
                readFixedArray(Array.get(val, i), c);
            }
        } else {
            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
        }
    }

    public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) {
        T val = null;
        final Class<?> componentType = cls.getComponentType();
        if (componentType == boolean.class) {
            val = (T) createBooleanArray();
        } else if (componentType == byte.class) {
            val = (T) createByteArray();
        } else if (componentType == char.class) {
            val = (T) createCharArray();
        } else if (componentType == int.class) {
            val = (T) createIntArray();
        } else if (componentType == long.class) {
            val = (T) createLongArray();
        } else if (componentType == float.class) {
            val = (T) createFloatArray();
        } else if (componentType == double.class) {
            val = (T) createDoubleArray();
        } else if (componentType == String.class) {
            val = (T) createStringArray();
        } else if (componentType.isArray()) {
            int length = readInt();
            if (length < 0) {
                return null;
            }
            if (length != dimensions[0]) {
                throw new BadParcelableException("Bad length: expected " + dimensions[0]
                        + ", but got " + length);
            }

            // Create a multi-dimensional array with an innermost component type and dimensions
            Class<?> innermost = componentType.getComponentType();
            while (innermost.isArray()) {
                innermost = innermost.getComponentType();
            }
            val = (T) Array.newInstance(innermost, dimensions);
            for (int i = 0; i < length; i++) {
                readFixedArray(Array.get(val, i));
            }
            return val;
        } else {
            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
        }

        // Check if val is null (which is OK) or has the expected size.
        // This check doesn't have to be multi-dimensional because multi-dimensional arrays
        // are created with expected dimensions.
        if (val != null && Array.getLength(val) != dimensions[0]) {
            throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
                    + Array.getLength(val));
        }
        return val;
    }

    public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls, @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) {
        T val = null;
        final Class<?> componentType = cls.getComponentType();
        if (Parcelable.class.isAssignableFrom(componentType)) {
            val = (T) createTypedArray(c);
        } else if (componentType.isArray()) {
            int length = readInt();
            if (length < 0) {
                return null;
            }
            if (length != dimensions[0]) {
                throw new BadParcelableException("Bad length: expected " + dimensions[0]
                        + ", but got " + length);
            }

            // Create a multi-dimensional array with an innermost component type and dimensions
            Class<?> innermost = componentType.getComponentType();
            while (innermost.isArray()) {
                innermost = innermost.getComponentType();
            }
            val = (T) Array.newInstance(innermost, dimensions);
            for (int i = 0; i < length; i++) {
                readFixedArray(Array.get(val, i), c);
            }
            return val;
        } else {
            throw new BadParcelableException("Unknown type for fixed-size array: " + componentType);
        }

        // Check if val is null (which is OK) or has the expected size.
        // This check doesn't have to be multi-dimensional because multi-dimensional arrays
        // are created with expected dimensions.
        if (val != null && Array.getLength(val) != dimensions[0]) {
            throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got "
                    + Array.getLength(val));
        }
        return val;
    }
}
